多线程到Event Loop
CPU、进程、线程
CPU可以类比为一个工厂,进程就相当于工厂的车间,而线程则是车间的工人。
CPU总是会运行一个进程,其他进程处于非运行状态,而一个进程又可以包括多个线程
,多个线程共享进程
的资源。
进程是CPU资源分配的最小的单位,线程是CPU调度的最小单位。单线程和多线程都是指在一个进程中的单与多。
浏览器与进程
对计算机来说,每一个程序都是一个进程
,一个程序通常会有很多功能模块对应的就是子进程
, 而通过子进程实现的应用程序就是所谓的多进程
,如浏览器,每一个Tab就是一个进程
浏览器的进程
1. 主进程
- 协调子进程、如创建、销毁
- 界面显示、交互
- 网络请求、文件访问
- 将渲染进程的内容绘制到用户界面
2. 第三方插件进程
- 每个插件会对应一个进程,使用时创建
3. GPU进程
- 绘制相关
4. 渲染进程(浏览器内核)
- 负责页面渲染、脚本执行、事件执行
- 每个Tab页面对应一个渲染进程
关于渲染进程重点
由进程和线程一对多的关系,然后梳理下该进程下包含哪些线程
GUI渲染线程
- 负责页面渲染、绘制
- 页面重绘回流时执行
- 与JS引擎线程互斥,防止渲染结果不可控制
JS线程
- 处理解析JS
- 只有一个线程(所谓的JS单线程)
- 与GUI渲染互斥,防止渲染结果不可控制
事件触发线程
- 控制事件渲染
- 将事件放入JS引擎所在的执行队列
定时器线程
setTimeout
与setInterval
- 定时任务不是由JS引擎计时,而是由定时器线程来工作
- 通知事件触发线程
异步线程
- 单独线程处理AJAX请求
- 完成时将回调交给事件触发线程
为什么JS是单线程?
早期硬件支持不行,且因为多线程的复杂性(加锁、编码复杂性),如果同时操作DOM,会导致DOM渲染结果不可预期(核心原因)
为什么渲染和JS互斥?
归根结底就是因为JS可以操作DOM,修改元素属性和渲染界面同时运行,那么最终渲染前后的元素可能就不同了。
Event Loop机制
- JS分
同步任务
与异步任务
- 同步任务都在JS线程执行,形成执行栈
- 事件触发线程会管理一个任务队列,异步任务的回调也会放入这个任务队列中
- 当同步任务执行栈完成后,JS引擎线程空闲,系统会调度任务队列,将异步任务回调添加到执行栈中执行
setTimeout/setInterval/XHR/Fetch等 代码本身是同步任务,而其中的回调才是异步任务。
setTimeut/setInterval 是由定时器线程计时,XHR/Fetch由异步线程管理, 最终都是交给事件触发线程管理的任务队列中
当同步任务执行完成后,JS引擎线程会访问事件触发线程,任务队列中是否有需要执行的回调函数,如果有就会交给JS引擎线程去执行
console.log('1)
setTimeout(()=>console.log(2))
console.log('3')
//1 3 2
宏任务
我们可以将任务队列中的任务分为宏任务与微任务。 通过JS引擎线程执行的主代码栈我们可以看作是一次宏任务。 并且为了能够使宏任务和DOM任务有序进行,通常在一个宏任务执行后,下一个宏任务执行前,GUI渲染开始工作对页面进行渲染。
注意,如
setTimeout、setInterval
也算是宏任务
document.body.style = 'background:blue';
setTimeout(()=>{
document.body.style = 'background:black';
})
这里你会发现,页面会先变成蓝色,然后立马变成黑色,这是因为代码块执行完成后(即第一个宏任务执行),GUI线程工作,渲染页面为蓝色, 跟着下一个宏任务(setTimeout) 页面变为黑色
微任务
除了宏任务,还存在微任务,代表如:Promise、nextTick、queueMicrotask
微任务的特点就是在宏任务结束后,下一个宏任务执行前, 立马进行执行的任务。(同理它也是在GUI渲染线程执行前的任务队列)
setTimeout(() => console.log(2));
Promise.resolve().then(() => {
console.log(3);
});
setTimeout(() => {
Promise.resolve().then(() => console.log(5));
console.log(4);
});
console.log(1);
// 1 3 2 4 5
梳理上面代码的执行过程, 首先是主代码块的同步任务执行(宏任务),输出1, 接着同时遇到下一个宏任务和微任务setTimeout/Promise
,此时由于处理微任务,输出3, 微任务执行完成后,没有其他微任务后,再执行宏任务,输出先进入任务队列的 2, 接着执行剩下的宏任务,此时这个宏任务内,会先将微任务加入微任务队列,由于存在同步代码
console.log(4)会先执行,执行完成后,再去任务队列执行剩下的微任务
梳理
- 执行宏任务(如果执行栈中没有就去任务队列获取)
- 先处理遇到的微任务,添加到任务队列中
- 宏任务执行完成后,先执行微任务队列的所有微任务
- 微任务完成后再执行宏任务,宏任务结束后开始GUI线程渲染
- GUI渲染完成后,JS线程接管开始下一个宏任务